package com.xyy.saas.payment.adpater.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fbank.openapi.sdk.client.OpenParameters;
import com.xyy.saas.payment.adpater.PaymentAdapter;
import com.xyy.saas.payment.adpater.assemble.FBankRequestParamAssemble;
import com.xyy.saas.payment.adpater.assemble.dto.WithdrawAccountDto;
import com.xyy.saas.payment.adpater.config.BankConfigManager;
import com.xyy.saas.payment.adpater.fumin.constants.ApiNameConstants;
import com.xyy.saas.payment.cores.vo.QueryFuminRecordBo;
import com.xyy.saas.payment.cores.vo.QueryAccountBalanceDO;
import com.xyy.saas.payment.adpater.fumin.pojo.domain.SeparateAccountDO;
import com.xyy.saas.payment.adpater.fumin.spring.SpringClient;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.enums.FBankTradeStatusEnum;
import com.xyy.saas.payment.common.enums.PictureEnum;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.cores.bo.*;
import com.xyy.saas.payment.cores.enums.*;
import com.xyy.saas.payment.cores.param.*;
import com.xyy.saas.payment.cores.vo.ContactInfo;
import com.xyy.saas.payment.cores.vo.OperatorInfo;
import com.xyy.saas.payment.dao.model.RefundOrder;
import com.xyy.saas.payment.dao.model.ThirdUserInfo;
import com.xyy.saas.payment.merchant.core.dto.*;
import com.xyy.saas.payment.merchant.core.service.AccountService;
import com.xyy.saas.payment.merchant.core.vo.*;
import com.xyy.saas.payment.payment.core.bo.RefundBO;
import com.xyy.saas.payment.payment.core.bo.WithdrawalBO;
import com.xyy.saas.payment.payment.core.dto.*;
import com.xyy.saas.payment.payment.core.service.PrePayOrderService;
import com.xyy.saas.payment.service.ThirdUserInfoService;
import com.xyy.saas.payment.service.UserService;
import com.xyy.saas.payment.util.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 富民2.0
 */
@Service
@Slf4j
public class FuminAdapter2 implements PaymentAdapter {
    @Value("${fmBank.stop.switch:false}")
    private Boolean fmBankStopSwitch;
    @Resource
    private FBankRequestParamAssemble paramAssemble;
    @Resource
    private SpringClient springClient;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private PrePayOrderService prePayOrderService;
    @Resource
    private BankConfigManager bankConfigManager;
    @Resource
    private UserService userService;
    @Resource
    private FuminAdapter fuminAdapter;
    @Resource
    private LeafUtil leafUtil;
    @Resource
    private ThirdUserInfoService thirdUserInfoService;
    @Autowired
    private AccountService accountService;

    /**
     * 创建预支付单
     */
    @Override
    public PrepayBo prepay(PrepayDto prepayDto) throws PaymentException {
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        return prePayOrderService.prepayOrderByPrepayDto(prepayDto);
    }

    /**
     * 提现申请
     */
    @Override
    public WithdrawalBO withdraw(WithdrawalDto withdrawalDto) throws PaymentException {
        log.info("FuminAdapter2#withdraw withdrawalDto={}", JSONUtils.toJSON(withdrawalDto));

        // 1.组装请求参数
        WithdrawAccountDto withdrawAccountDto = paramAssemble.getWithdrawAccountInfo(withdrawalDto.getUserId());
        String merchantNo = bankConfigManager.getFuminBankMch(withdrawalDto.getBusinessIdType());
        JSONObject requestParam = new JSONObject();
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("merchantOrderNo", withdrawalDto.getWithdrawalNo());
        requestParam.put("tradeAmount", AmountCastUtil.castFen2Yuan(withdrawalDto.getAmount()));
        requestParam.put("payerMerchantCustNo", withdrawalDto.getUserId());
        requestParam.put("payerAccount", withdrawAccountDto.getPayerAccountNo());
        requestParam.put("payeeName", withdrawAccountDto.getPayeeName());
        requestParam.put("payeeAccount", withdrawAccountDto.getPayeeAccountNo());

        // 手续费为0不传该字段
        if(withdrawalDto.getFee() != NumberUtils.INTEGER_ZERO) {
            requestParam.put("poundageAmount", AmountCastUtil.castFen2Yuan(withdrawalDto.getFee()));
        }

        requestParam.put("tradeRemark", withdrawalDto.getDescription());
        requestParam.put("notifyUrl", ApolloUtil.getProperty("fmBank.callBack.withdrawal"));

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.WITHDRAW_APPLY).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#withdraw request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse result = FBankResponse.parse(response);
        String activeCodeSerialNo = result.getResultString("activeCodeSerialNo");

        // 4.缓存鉴权验证码流水号
        if(StringUtils.isNotBlank(activeCodeSerialNo)) {
            // 提现短信验证码30分钟
            redisUtil.set(Constants.ACTIVE_CODE_MSG_INFO + withdrawalDto.getWithdrawalNo(),
                    activeCodeSerialNo, RedisUtil.MINUTE_30);
        }

        // 5.构建响应参数
        WithdrawalBO withdrawalBO = new WithdrawalBO();
        withdrawalBO.setChannelWithdrawNo(result.getResultString("bizNo"));
        withdrawalBO.setFinishTime(result.getResultString("finishTime"));
        withdrawalBO.setUserName(withdrawAccountDto.getPayeeName());
        withdrawalBO.setStatus(result.getResultString("tradeStatus"));
        withdrawalBO.setNeedAuth(StringUtils.isNotBlank(activeCodeSerialNo));
        withdrawalBO.setWithdrawalNo(withdrawalDto.getWithdrawalNo());
        return withdrawalBO;
    }

    /**
     * 提现鉴权
     */
    @Override
    public WithdrawalBO withdrawAuth(WithdrawAuthDto withdrawAuthDto) throws PaymentException {
        log.info("FuminAdapter2#withdrawAuth withdrawAuthDto={}", JSONUtils.toJSON(withdrawAuthDto));

        // 0.参数校验
        WithdrawAuthDto.checkParam(withdrawAuthDto);
        String activeCodeSerialNo = redisUtil.get(Constants.ACTIVE_CODE_MSG_INFO + withdrawAuthDto.getWithdrawalNo());

        if(StringUtils.isBlank(activeCodeSerialNo)) {
            throw new PaymentException(ResultCodeEnum.ACTIVE_CODE_ERROR);
        }

        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(withdrawAuthDto.getBusinessIdType());
        JSONObject requestParam = new JSONObject();
        requestParam.put("bizNo", withdrawAuthDto.getChannelWithdrawNo());
        requestParam.put("activeCode", withdrawAuthDto.getActiveCode());
        requestParam.put("activeCodeSerialNo", activeCodeSerialNo);

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.WITHDRAW_AUTH).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#withdrawAuth request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse result = FBankResponse.parse(response);

        // 4.构建响应参数
        WithdrawalBO withdrawalBO = new WithdrawalBO();
        withdrawalBO.setChannelWithdrawNo(result.getResultString("bizNo"));
        withdrawalBO.setFinishTime(result.getResultString("finishTime"));
        withdrawalBO.setStatus(result.getResultString("tradeStatus"));
        return withdrawalBO;
    }

    @Override
    public UserPicBo uploadPic(UserDto userDto) throws PaymentException {
        UserPicBo userPicBo = new UserPicBo();
        userPicBo.setAccountNo(userDto.getAccNo());
        userPicBo.setUser(userDto.getUserId());
        userPicBo.setThirdType(Constants.QIFUTONG);
        userPicBo.setType(userDto.getAccountType());
        return userPicBo;
    }

    /**
     * 企业开基本户
     */
    @Override
    public SubAppBo createMerchant(UserDto userDto) throws PaymentException {
        log.info("FuminAdapter2#createMerchant userDto={}", JSONUtils.toJSON(userDto));
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(userDto.getBusinessIdType());
        JSONObject requestParam = new JSONObject();
        // 会员数据
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", userDto.getUserId());
        requestParam.put("custMobile", userDto.getMobile());

        // 企业信息
        requestParam.put("entName", userDto.getName());
        requestParam.put("enterprisePhone", userDto.getEnterprisePhone());
        requestParam.put("category", userDto.getMerchantCategory());
        requestParam.put("enterpriseCardType", "UNIFIED_SOCIAL_CREDIT_CODE");
        requestParam.put("enterpriseRegistrationNo", userDto.getIdentityInfo().getId());
        requestParam.put("businessLocation", userDto.getAddress());
        requestParam.put("businessLocationUrl", uploadPicture(merchantNo, userDto.getPicMap().get(String.valueOf(PictureEnum.SHOP_ENTRANCE_PIC.getCode()))));
        requestParam.put("businessLicenseUrl", uploadPicture(merchantNo, userDto.getPicMap().get(String.valueOf(PictureEnum.BUSINESS_CERT_PIC.getCode()))));

        // 结算卡信息
        Recipient recipient = userDto.getSettleAccountDto().getRecipient();
        requestParam.put("acct", recipient.getAccount());
        requestParam.put("businessType", recipient.getType());

        if(Recipient.PUBLIC.equals(recipient.getType())) {
            requestParam.put("protocolNo", recipient.getSubBankCode());
        }
        else {
            requestParam.put("acctMobilePhone", recipient.getMobile());
        }

        // 法人信息
        ContactInfo contactInfo = userDto.getContactInfos().get(NumberUtils.INTEGER_ZERO);
        requestParam.put("corporationName", contactInfo.getContactName());
        requestParam.put("corporationIdType", "IDENTITY_CARD");
        requestParam.put("corporationIdNo", contactInfo.getContactCertNo());
        requestParam.put("corporationIdStartDate", contactInfo.getContactCertValidFrom());
        requestParam.put("corporationIdEndDate", contactInfo.getContactCertValidUntil());
        requestParam.put("corporationPhone", contactInfo.getContactCertMobile());
        requestParam.put("corporationIdCardFrontUrl", uploadPicture(merchantNo, userDto.getPicMap().get(String.valueOf(PictureEnum.LEGAL_CERT_FRONT.getCode()))));
        requestParam.put("corporationIdCardBackUrl", uploadPicture(merchantNo, userDto.getPicMap().get(String.valueOf(PictureEnum.LEGAL_CERT_BACK.getCode()))));

        // 授权人信息
        OperatorInfo operatorInfo = userDto.getOperatorInfo();
        requestParam.put("operatorName", operatorInfo.getName());
        requestParam.put("operatorIdType", "IDENTITY_CARD");
        requestParam.put("operatorIdNo", operatorInfo.getCardNo());
        requestParam.put("operatorPhone", operatorInfo.getPhone());
        requestParam.put("operatorCertificateUrl", uploadPicture(merchantNo, operatorInfo.getCertificateUrl()));

        // 短信验证码
        requestParam.put("activeCode", JSONUtils.parse2Map(userDto.getExtraMap(), String.class, String.class).get("activeCode"));
        requestParam.put("activeCodeSerialNo", getActiveMsgInfoFromRedis(operatorInfo.getPhone()));

        // 股东信息
        requestParam.put("shareholderList", userDto.getShareHolderList());

        // 受益人信息
        requestParam.put("beneficiaryList", userDto.getBeneficiaryList());

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.ENTERPRISE_REGISTER).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#createMerchant request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse result = FBankResponse.parse(response);

        // 4.保存虚户号
        userService.updateThirdUserIdByUserId(userDto.getUserId(), Constants.QIFUTONG, result.getResultString("accountNo"));
        userDto.setAccNo(result.getResultString("accountCoreNo"));

        // 5.构建响应参数
        SubAppBo subAppBo = new SubAppBo();
        subAppBo.setState(UserApplyStateEnum.SUCCEEDED);
        subAppBo.setAccountNo(result.getResultString("accountCoreNo"));
        subAppBo.setThirdType(Constants.QIFUTONG);
        return subAppBo;
    }

    /**
     * 企业开户信息修改
     */
    @Override
    public AccountModifyBo modifyMerchant(MerchantAccountModifyDto param) throws PaymentException {
        log.info("FuminAdapter2#modifyMerchant param={}", JSONUtils.toJSON(param));
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(param.getBusinessIdType().getType());
        JSONObject requestParam = new JSONObject();
        // 会员数据
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", param.getAccountId());

        // 申请单号
        requestParam.put("applyNo", param.getApplyNo());

        // 企业信息
        requestParam.put("enterprisePhone", param.getEnterprisePhone());
        requestParam.put("businessLocation", param.getBusinessLocation());
        requestParam.put("businessLocationUrl", uploadPicture(merchantNo, param.getBusinessLocationUrl()));
        requestParam.put("businessLicenseUrl", uploadPicture(merchantNo, param.getBusinessLicenseUrl()));

        // 法人信息
        requestParam.put("corporationName", param.getCorporationName());
        requestParam.put("corporationIdNo", param.getCorporationIdNo());
        requestParam.put("corporationIdStartDate", param.getCorporationIdStartDate());
        requestParam.put("corporationIdEndDate", param.getCorporationIdEndDate());
        requestParam.put("corporationIdCardFrontUrl", uploadPicture(merchantNo, param.getCorporationIdCardFrontUrl()));
        requestParam.put("corporationIdCardBackUrl", uploadPicture(merchantNo, param.getCorporationIdCardBackUrl()));
        requestParam.put("corporationPhone", param.getCorporationPhone());

        // 授权人
        requestParam.put("operatorName", param.getOperatorName());
        requestParam.put("operatorIdNo", param.getOperatorIdNo());
        requestParam.put("operatorPhone", param.getOperatorPhone());
        requestParam.put("operatorCertificateUrl", uploadPicture(merchantNo, param.getOperatorCertificateUrl()));

        // 股东
        requestParam.put("shareholderList", param.getShareHolderList());

        // 受益人
        requestParam.put("beneficiaryList", param.getBeneficiaryList());

        // 短信验证码
        requestParam.put("activeCode", param.getActiveCode());
        String activeCodeSerialNo = getActiveMsgInfoFromRedis(param.getMobile());

        if(StringUtils.isBlank(activeCodeSerialNo) && StringUtils.isNotBlank(param.getOperatorPhone())) {
            activeCodeSerialNo = getActiveMsgInfoFromRedis(param.getOperatorPhone());
        }

        requestParam.put("activeCodeSerialNo", activeCodeSerialNo);

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.ENTERPRISE_REGISTER_MODIFY).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#modifyMerchant request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse.parse(response);

        // 4.构建响应参数
        return AccountModifyBo.builder().applyNo(param.getApplyNo()).build();
    }

    /**
     * 个人开户
     */
    @Override
    public SubAppBo createPersonal(UserDto userDto) throws PaymentException {
        log.info("FuminAdapter2#createPersonal userDto={}", JSONUtils.toJSON(userDto));
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(userDto.getBusinessIdType());
        JSONObject requestParam = new JSONObject();
        // 会员数据
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", userDto.getUserId());
        requestParam.put("custMobile", userDto.getMobile());

        // 开户人信息
        IdentityInfo identityInfo = userDto.getIdentityInfo();
        requestParam.put("custName", userDto.getName());
        requestParam.put("custIdType", "IDENTITY_CARD");
        requestParam.put("custIdNo", identityInfo.getId());
        requestParam.put("cardStartDate", identityInfo.getValidFrom());
        requestParam.put("cardEndDate", identityInfo.getValidUntil());
        requestParam.put("homeAddr", userDto.getAddress());
        requestParam.put("professional", ProfessionEnum.getProfession(identityInfo.getProfession()));
        requestParam.put("idCardFrontUrl", uploadPicture(merchantNo, userDto.getPicMap().get(String.valueOf(PictureEnum.PERSONAL_CERT_FRONT.getCode()))));
        requestParam.put("idCardBackUrl", uploadPicture(merchantNo, userDto.getPicMap().get(String.valueOf(PictureEnum.PERSONAL_CERT_BACK.getCode()))));

        // 结算信息
        Recipient recipient = userDto.getSettleAccountDto().getRecipient();
        requestParam.put("acct", recipient.getAccount());
        requestParam.put("acctMobilePhone", recipient.getMobile());

        // 短信验证码
        requestParam.put("activeCode", JSONUtils.parse2Map(userDto.getExtraMap(), String.class, String.class).get("activeCode"));

        String activeCodeSerialNo = getActiveMsgInfoFromRedis(userDto.getMobile());

        if(StringUtils.isBlank(activeCodeSerialNo)) {
            activeCodeSerialNo = getActiveMsgInfoFromRedis(recipient.getMobile());
        }

        requestParam.put("activeCodeSerialNo", activeCodeSerialNo);

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.PERSONAL_REGISTER).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#createPersonal request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse result = FBankResponse.parse(response);

        // 4.保存虚户号
        userService.updateThirdUserIdByUserId(userDto.getUserId(), Constants.QIFUTONG, result.getResultString("accountNo"));
        userDto.setAccNo(result.getResultString("accountNo"));

        // 5.构建响应参数
        SubAppBo subAppBo = new SubAppBo();
        subAppBo.setState(UserApplyStateEnum.SUCCEEDED);
        subAppBo.setAccountNo(result.getResultString("accountNo"));
        subAppBo.setThirdType(Constants.QIFUTONG);
        return subAppBo;
    }

    /**
     * 个人开户信息修改
     */
    @Override
    public AccountModifyBo modifyPersonal(PersonalAccountModifyDto param) throws PaymentException {
        log.info("FuminAdapter2#modifyPersonal param={}", JSONUtils.toJSON(param));
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(param.getBusinessIdType().getType());
        JSONObject requestParam = new JSONObject();
        // 会员数据
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", param.getAccountId());

        // 申请单号
        requestParam.put("applyNo", param.getApplyNo());

        // 开户人信息
        requestParam.put("cardStartDate", param.getCardStartDate());
        requestParam.put("cardEndDate", param.getCardEndDate());
        requestParam.put("homeAddr", param.getHomeAddr());
        requestParam.put("professional", ProfessionEnum.getProfession(param.getProfessional()));
        requestParam.put("idCardFrontUrl", uploadPicture(merchantNo, param.getIdCardFrontUrl()));
        requestParam.put("idCardBackUrl", uploadPicture(merchantNo, param.getIdCardBackUrl()));

        // 短信验证码
        requestParam.put("activeCode", param.getActiveCode());
        requestParam.put("activeCodeSerialNo", getActiveMsgInfoFromRedis(param.getMobile()));

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.PERSONAL_REGISTER_MODIFY).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#modifyPersonal request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse.parse(response);

        // 4.构建响应参数
        return AccountModifyBo.builder().applyNo(param.getApplyNo()).build();
    }

    /**
     * 企业绑卡
     */
    @Override
    public String merchantCardBind(MerchantCardBindDto merchantCardBindDto) throws PaymentException {
        log.info("FuminAdapter2#merchantCardBind merchantCardBindDto={}", JSONUtils.toJSON(merchantCardBindDto));
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(merchantCardBindDto.getBusinessIdType());
        JSONObject requestParam = new JSONObject();
        // 会员信息
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", merchantCardBindDto.getUserId());

        // 卡信息
        requestParam.put("bindOperateType", "SETTLEMENT_BIND");
        requestParam.put("acct", merchantCardBindDto.getAccount());
        requestParam.put("businessType", merchantCardBindDto.getCardType());

        if(Recipient.PUBLIC.equals(merchantCardBindDto.getCardType())) {
            requestParam.put("protocolNo", merchantCardBindDto.getBranchBankCd());
        }
        else {
            requestParam.put("acctMobilePhone", merchantCardBindDto.getMobile());
        }

        // 短信验证码
        requestParam.put("activeCode", JSONUtils.parse2Map(merchantCardBindDto.getExtra(), String.class, String.class).get("activeCode"));
        requestParam.put("activeCodeSerialNo", getActiveMsgInfoFromRedis(merchantCardBindDto.getMobile()));

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.ENTERPRISE_BIND_CARD).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#merchantCardBind request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse.parse(response);

        return "";
    }

    /**
     * 个人绑卡
     */
    @Override
    public void personalCardBind(PersonalCardBindDto personalCardBindDto) throws PaymentException {
        log.info("FuminAdapter2#personalCardBind merchantCardBindDto={}", JSONUtils.toJSON(personalCardBindDto));
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(personalCardBindDto.getBusinessIdType());
        JSONObject requestParam = new JSONObject();
        // 会员信息
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", personalCardBindDto.getUserId());

        // 卡信息
        requestParam.put("bindOperateType", "SETTLEMENT_BIND");
        requestParam.put("acct", personalCardBindDto.getAccount());
        requestParam.put("acctMobilePhone", personalCardBindDto.getMobile());

        // 短信验证码
        requestParam.put("activeCode", JSONUtils.parse2Map(personalCardBindDto.getExtra(), String.class, String.class).get("activeCode"));
        requestParam.put("activeCodeSerialNo", getActiveMsgInfoFromRedis(personalCardBindDto.getMobile()));

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.PERSONAL_BIND_CARD).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#personalCardBind request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse.parse(response);
    }

    /**
     * 解绑卡
     */
    @Override
    public void unBindCard(UnBindCardDto unBindCardDto) throws PaymentException {
        log.info("FuminAdapter2#unBindCard unBindCardDto={}", JSONUtils.toJSON(unBindCardDto));
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(unBindCardDto.getBusinessIdType());
        JSONObject requestParam = new JSONObject();

        // 会员信息
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", unBindCardDto.getUserId());

        // 卡信息
        requestParam.put("unBindOperateType", unBindCardDto.getOperateType());
        requestParam.put("acct", unBindCardDto.getAccount());

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.UNBIND_CARD).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#unBindCard request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse.parse(response);
    }

    /**
     * 查询账户余额
     */
    @Override
    @Retryable
    public UserBo queryAccount(QueryAccountDto queryAccountDto) throws PaymentException {
        log.info("FuminAdapter2#queryAccount queryAccountDto={}", JSONUtils.toJSON(queryAccountDto));

        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(queryAccountDto.getBusinessIdType());
        JSONObject requestParam = new JSONObject();

        // 会员信息
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", queryAccountDto.getUserId());

        // 账户信息
        requestParam.put("accountCoreNo", queryAccountDto.getThirdUserId());

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.QUERY_ACCOUNT_INFO).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#queryAccount request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse result = FBankResponse.parse(response);

        // 4.构建响应参数
        List<Map<String, String>> item = result.getResultList("accountInfoList");
        return UserBo.builder().
                availableBalance(AmountCastUtil.castYuan2FenLong(item.get(NumberUtils.INTEGER_ZERO).get("availableBalance"))).
                withdrawableBalance(AmountCastUtil.castYuan2FenLong(item.get(NumberUtils.INTEGER_ZERO).get("availableBalance"))).
                frozenAmount(AmountCastUtil.castYuan2FenLong(item.get(NumberUtils.INTEGER_ZERO).get("frozenAmount"))).
                build();
    }

    /**
     * 查询退款状态（对外提供查询退款状态接口）
     */
    @Override
    @Deprecated
    public QueryResultBo queryRefundStatus(RefundOrder refundOrder) throws PaymentException {
        /**
         * TODO 待富民给新接口
         */
        return fuminAdapter.queryRefundStatus(refundOrder);
    }

    /**
     * 查询交易状态（支付平台内部逻用：退款，转账）
     */
    @Override
    public QueryResultBo queryTransactionStatus(QueryTransactionStatusDto queryTransactionStatusDto) throws PaymentException {
        /**
         * TODO 待富民给新接口
         */
        return fuminAdapter.queryTransactionStatus(queryTransactionStatusDto);
    }

    /**
     * 查询交易状态（对外提供查询支付，退款，提现状态接口）
     */
    @Override
    public TransactionResultBO queryTransaction(TransactionParam param) throws PaymentException {
        /**
         * TODO 待富民给新接口
         */
        return fuminAdapter.queryTransaction(param);
    }

    /**
     * 支付
     */
    @Override
    public PayBo pay(PayDto payDto) throws PaymentException {
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        /**
         * TODO 不变
         */
        return fuminAdapter.pay(payDto);
    }

    /**
     * 退款
     */
    @Override
    public RefundBO refund(RefundDto refundDto) throws PaymentException {
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        /**
         * TODO 不变
         */
        return fuminAdapter.refund(refundDto);
    }

    /**
     * 发送短信验证码
     */
    @Override
    public void sendActiveCodeByPhoneNum(ActiveCodeDto activeCodeDto) throws PaymentException {
        log.info("FuminAdapter2#sendActiveCodeByPhoneNum activeCodeDto={}", JSONUtils.toJSON(activeCodeDto));

        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(activeCodeDto.getBusinessIdType());
        JSONObject requestParam = new JSONObject();
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("phone", activeCodeDto.getPhoneNum());
        requestParam.put("userBizType", AccountTypeEnum.getOperateType2(activeCodeDto.getAccountType(),
                accountService.checkBaseOpenAccountComplete(activeCodeDto.getAccountId())));
        requestParam.put("custMerchantNo", activeCodeDto.getAccountId());

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.SEND_VERIFICATION_CODE).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#sendActiveCodeByPhoneNum request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse result = FBankResponse.parse(response);

        // 4.缓存短信验证码流水号
        redisUtil.set(Constants.ACTIVE_CODE_MSG_INFO + activeCodeDto.getPhoneNum(),
                result.getResultString("activeCodeSerialNo"), RedisUtil.MINUTE_10);
    }

    /**
     * 进件
     */
    @Override
    public CreateAllPayBo createAllPay(CreateAllPayDto createAllPayDto) throws PaymentException {
        if (fmBankStopSwitch) {
            throw new PaymentException(ResultCodeEnum.FM_CHANNEL_CLOSED);
        }
        /**
         * TODO 不变
         */
        return fuminAdapter.createAllPay(createAllPayDto);
    }

    /**
     * 开支付功能状态查询
     */
    @Override
    public Map<String, Boolean> queryOpenPay(String accountId, String businessIdType) throws PaymentException {
        /**
         * TODO 不变
         */
        return fuminAdapter.queryOpenPay(accountId, businessIdType);
    }

    /**
     * 账户转账
     */
    @Override
    public SeparateAccountsBo separateAccountBetweenUser(SeparateAccountDO separateAccountDO) throws PaymentException {
        log.info("FuminAdapter2#separateAccountBetweenUser separateAccountDO={}", JSONUtils.toJSON(separateAccountDO));

        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(separateAccountDO.getBusinessOrderType());
        JSONObject requestParam = new JSONObject();
        requestParam.put("merchantNo", merchantNo);
        // 单号和金额
        requestParam.put("merchantOrderNo", separateAccountDO.getSeparateNo());
        requestParam.put("tradeAmount", AmountCastUtil.castFen2Yuan(separateAccountDO.getTradeAmount()));

        if(StringUtils.isNotBlank(separateAccountDO.getTradeRemark())) {
            requestParam.put("tradeRemark", separateAccountDO.getTradeRemark());
        }

        // 出款人信息
        requestParam.put("payerMerchantCustNo", separateAccountDO.getCustMerchantNo());
        requestParam.put("payerAccount", separateAccountDO.getAccountOut());

        // 收款人信息
        requestParam.put("payeeMerchantCustNo", separateAccountDO.getCustNoIn());
        requestParam.put("payeeAccount", separateAccountDO.getAccountIn());
        requestParam.put("payeeName", separateAccountDO.getNameIn());

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.ACCOUNT_TRANSFER).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#separateAccountBetweenUser request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse.parse(response);

        // 4.构建响应参数
        return SeparateAccountsBo.build(separateAccountDO.getSeparateNo());
    }

    /**
     * 查询账户流水
     */
    @Override
    public QueryFuminRecordBo queryBalanceRecord(QueryAccountBalanceDO queryAccountBalanceDO) throws PaymentException {
        log.info("FuminAdapter2#queryBalanceRecord queryAccountBalanceDO={}", JSONUtils.toJSON(queryAccountBalanceDO));

        // 1.组装请求参数
        String merchantNo = bankConfigManager.getFuminBankMch(queryAccountBalanceDO.getBusinessOrderType());
        JSONObject requestParam = new JSONObject();
        // 会员信息
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("accountCoreNo", queryAccountBalanceDO.getThirdUserId());

        // 查询时间范围
        requestParam.put("startDate", QueryAccountBalanceDO.fixDateTime(queryAccountBalanceDO.getBeginTime()));
        requestParam.put("endDate", QueryAccountBalanceDO.fixDateTime(queryAccountBalanceDO.getEndTime()));

        // 分页信息
        HashMap<String, String> pageInfo = new HashMap<>();
        pageInfo.put("pageNo", String.valueOf(queryAccountBalanceDO.getPageNo()));
        pageInfo.put("pageSize", "20");
        requestParam.put("pageInfo", pageInfo);

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.QUERY_ACCOUNT_BALANCE_RECORD).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#queryBalanceRecord request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse result = FBankResponse.parse(response);

        // 4.构建响应参数
        return QueryFuminRecordBo.build(result.getResult());
    }

    /**
     * 冻结
     */
    @Override
    public FrozenBo frozenBalance(FrozenDto frozenDto) throws PaymentException {
        log.info("FuminAdapter2#frozenBalance frozenDto={}", JSONUtils.toJSON(frozenDto));

        // 1.组装请求参数
        ThirdUserInfo thirdUserInfo =  thirdUserInfoService.getThirdUserInfoByUserId(frozenDto.getAccountId());

        if(thirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        String merchantNo = bankConfigManager.getFuminBankMch(frozenDto.getBusinessOrderType());
        JSONObject requestParam = new JSONObject();
        // 会员信息
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", frozenDto.getAccountId());

        // 资金信息
        requestParam.put("accountCoreNo", thirdUserInfo.getThirdUserId());
        requestParam.put("frozenFlowNo", frozenDto.getBizChannelOrderId());
        requestParam.put("frozenAmount", frozenDto.getAmount());
        requestParam.put("frozenReason", "虚户资金冻结");

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.ACCOUNT_AMOUNT_FROZEN).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#frozenBalance request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse.parse(response);

        // 4.构建响应参数
        return FrozenBo.build(frozenDto.getBizChannelOrderId());
    }

    /**
     * 解冻
     */
    @Override
    public UnfrozenBo unfrozenBalance(UnfrozenDto unfrozenDto) throws PaymentException {
        log.info("FuminAdapter2#unfrozenBalance unfrozenDto={}", JSONUtils.toJSON(unfrozenDto));

        String merchantNo = bankConfigManager.getFuminBankMch(unfrozenDto.getBusinessOrderType());
        JSONObject requestParam = new JSONObject();
        // 会员信息
        requestParam.put("merchantNo", merchantNo);

        // 资金信息
        requestParam.put("unFrozenFlowNo", unfrozenDto.getBizChannelOrderId());
        requestParam.put("oriFlowNo", unfrozenDto.getOriginBizChannelOrderId());
        requestParam.put("unFrozenAmount", unfrozenDto.getAmount());
        requestParam.put("unFrozenReason", "虚户资金解冻");


        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.ACCOUNT_AMOUNT_UNFROZEN).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#unfrozenBalance request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        FBankResponse.parse(response);

        // 4.构建响应参数
        return UnfrozenBo.build(unfrozenDto.getBizChannelOrderId());
    }

    /**
     * 上传图片
     */
    public String uploadPicture(String merchantNo, String url) throws PaymentException {
        if(StringUtils.isBlank(url)) {
            return null;
        }

        log.info("FuminAdapter2#uploadPicture merchantNo={}, url={}", merchantNo, url);
        String serialNumber = leafUtil.getSnowflakeId();
        JSONObject request = new JSONObject();
        JSONObject params = new JSONObject();
        request.put("serialNumber", serialNumber);
        request.put("merchantNo", merchantNo);
        params.put("request", request);

        // 1.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.FILE_UPLOAD)
        .fileBytes(HttpClientUtil.getStream(url))
        .fileType(PicUtil.parsePicType(url))
        .serialNumber(serialNumber)
        .param(params.toJSONString());

        String response = springClient.uploadFile(builder.build(), merchantNo);
        log.info("FuminAdapter2#uploadPicture url:{} response:{}", url, response);

        // 2.解析响应参数
        FBankResponse result = FBankResponse.parse(response);

        // 3.构建响应参数
        return result.getResultString("path");
    }

    /**
     * 操作51入金白名单
     */
    public Object operate51WhiteList(FBank51WhiteList fBank51WhiteList) throws PaymentException {
        log.info("FuminAdapter2#operate51WhiteList fBank51WhiteList={}", JSONUtils.toJSON(fBank51WhiteList));

        ThirdUserInfo thirdUserInfo =  thirdUserInfoService.getThirdUserInfoByUserId(fBank51WhiteList.getAccountId());

        if(thirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        String merchantNo = bankConfigManager.getFuminBankMch(fBank51WhiteList.getBusinessIdType());
        JSONObject requestParam = new JSONObject();
        // 会员信息
        requestParam.put("merchantNo", merchantNo);
        requestParam.put("custMerchantNo", fBank51WhiteList.getAccountId());
        requestParam.put("accountCoreNo", thirdUserInfo.getThirdUserId());

        requestParam.put("operateType", fBank51WhiteList.getOperateType());

        // 卡信息
        requestParam.put("cardInfoList", fBank51WhiteList.getWhiteList());

        // 2.发送请求
        OpenParameters.Builder builder = new OpenParameters.Builder();
        builder.apiName(ApiNameConstants.WHITE_LIST_51).param(requestParam.toJSONString());
        String response = springClient.sendUnify(builder.build(), merchantNo);
        log.info("FuminAdapter2#operate51WhiteList request:{} response:{}", JSONUtils.toJSON(requestParam), response);

        // 3.解析响应参数
        return FBankResponse.parse(response);
    }

    /**
     * 获取短信验证码
     */
    private String getActiveMsgInfoFromRedis(String key) throws PaymentException {
        String msgInfo = redisUtil.get(Constants.ACTIVE_CODE_MSG_INFO + key);

        if(msgInfo == null) {
            throw new PaymentException(ResultCodeEnum.ACTIVE_CODE_ERROR);
        }

        return msgInfo;
    }
}

/**
 * 富民响应结构体
 */
@Getter
@Setter
class FBankResponse {
    private String status;
    private String description;
    private String code;
    private String timestamp;
    private Map<String, Object> result;

    /**
     * 是否成功
     * @return
     */
    public boolean success() {
        return FBankTradeStatusEnum.SUCCESS.name().equals(status) || FBankTradeStatusEnum.PROCESSING.name().equals(status);
    }

    /**
     * 获取结果
     */
    public String getResultString(String key) {
        return result == null ? null : (String) result.get(key);
    }

    /**
     * 获取结果
     */
    public List<Map<String, String>> getResultList(String key) {
        return result == null ? null : (List<Map<String, String>>) result.get(key);
    }

    /**
     * 解析响应
     * @return
     */
    public static FBankResponse parse(String body) throws PaymentException {
        if(StringUtils.isBlank(body)) {
            throw new PaymentException(ResultCodeEnum.FBANK_SYSTEM_ERROR);
        }

        JSONObject param = JSON.parseObject(body);
        FBankResponse response = new FBankResponse();
        response.setStatus(param.getString("status"));
        response.setDescription(param.getString("description"));
        response.setCode(param.getString("code"));
        response.setTimestamp(param.getString("timestamp"));

        // 失败直接抛出业务异常
        if(!response.success()) {
            throw new PaymentException(response.getCode(), response.getDescription());
        }

        response.setResult(JSONUtils.parse2Map(param.getString("result"), String.class, Object.class));
        return response;
    }
}
